﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;

using DC = System.Drawing.Color;

namespace WallpaperGenerator {
	public sealed class Wallpaper {
        public string Name { get; set; }

		public int Width {
			get { return Bitmap.Width; }
		}

		public int Height {
			get { return Bitmap.Height; }
		}

        // a safe version of pixel blitting
		public Color this[uint x, uint y]
        {
			get {
				if ( x < Bitmap.Width && y < Bitmap.Height ) {
					return Bitmap.GetPixel((int )x, (int )y);
				} else {
					return Color.Empty;
				}
			}
			set {
                if (x < Bitmap.Width && y < Bitmap.Height)
                {
                    Bitmap.SetPixel((int )x, (int )y, value);
				}
			}
		}

		public Wallpaper( string name, int width, int height ) {
			this.Name = name;
            Create(width, height);
		}

        private Bitmap bmp;
        public Bitmap Bitmap
        {
            get { return bmp; }
            set { bmp = value;  }
        }

        public void Create(int w, int h)
        {
            this.Bitmap = new Bitmap(w, h);
            Graphics.FromImage(this.Bitmap).Clear(DC.FromArgb(0, 0, 0, 0));
        }

		public void ApplyEffect( Effect effect ) {
			effect.Apply( this.Bitmap );
		}

		public void Save( bool dither ) {
			if ( dither ) {
				this.Dither();
			}

            string filename = string.Format( "{0}-{1}x{2}.png", this.Name, this.Width, this.Height );

			int version = 0;

			while ( File.Exists( filename ) ) {
				if ( version > 1000 ) {
					throw new Exception( "Could not render any more desktops with name " + this.Name );
				}

				filename = string.Format( "{0}-{1}x{2}-{3}.png", this.Name, this.Width, this.Height, ++version );
			}

			Bitmap.Save( filename );
		}

		private void Dither() {
			for ( uint x = 0; x < this.Width; ++x ) {
				for ( uint y = 0; y < this.Height; ++y ) {
					Color oldPixel = this[x, y];
					Color newPixel = GetClosestPaletteColor( oldPixel );

					this[x, y] = newPixel;

					Color error = oldPixel - newPixel;

					this[x + 1, y] = this[x + 1, y] + ( 7.0 / 16.0 ) * error;
					this[x - 1, y + 1] = this[x - 1, y + 1] + ( 3.0 / 16.0 ) * error;
					this[x, y + 1] = this[x, y + 1] + ( 5.0 / 16.0 ) * error;
					this[x + 1, y + 1] = this[x + 1, y + 1] + ( 1.0 / 16.0 ) * error;
				}
			}
		}

		private static Color GetClosestPaletteColor( Color to ) {
			// we need a slight loss of accuracy so we round it to
			// the nearest equivalent system.drawing.color
			return (Color)( (System.Drawing.Color)to );
		}
	}
}
